02 · Workflow 编排:路径写死反而更好
零、开始之前
前置:01 ReAct。会用 ThreadPoolExecutor 更好,不会本篇也会讲。
产出:判断一个需求该不该用 Agent,并把不该用的那部分用三种基本形状搭出来。
上一篇教你把方向盘交给模型,这一篇教你什么时候把它拿回来 —— 而且多数 时候你都该拿回来。
一、先看一个真实问题
你要做一个客服工单处理系统。用户发来一句话,系统要给出回复。
工单大概分三类:账单问题、技术故障、退款申请。三类问题的处理方式完全不同 —— 账单要查系统、技术要问日志、退款要走审批。
用 01 的 ReAct 怎么做? 给模型三个工具(查账单、查日志、发起退款),让它自己判断该调哪个。能跑。但上线之后你会遇到:
| 问题 | 具体表现 |
|---|---|
| 贵 | 每个工单至少 2 次模型调用(判断 + 回答),复杂的 5-6 次 |
| 慢 | 每一轮都是一次完整的网络往返,P99 延迟根本控不住 |
| 不可预测 | 同一个工单,今天走了 3 步,明天走了 5 步 |
| 没法测 | 你没办法写单元测试,因为路径每次都不一样 |
| 出错难查 | 客户投诉「回复驴唇不对马嘴」,你要翻一长串对话历史才知道哪步歪了 |
而实际上,这个业务的路径是完全确定的:判断类型 → 走对应流程 → 出回复。三条路,就这三条。
既然路径是确定的,为什么要让模型每一步都重新决定一次?
二、那直接写一个大 prompt 行不行
你可能想:那我把三种情况都写进一个 prompt,让模型一次搞定。
prompt = """
你是客服。判断用户问题属于账单/技术/退款哪一类,
然后按对应的规范回复。账单类要包含账期和金额,
技术类要给出排查步骤,退款类要说明审批流程和时限。
用户问题:{input}
"""
这也能跑,但质量会明显不如拆开。原因很实在:
一次要求模型做太多事,它每件都会做得敷衍。 你让它同时「判断类型 + 遵守该类型的格式规范 + 组织语言」,它的注意力被摊薄了。实测下来,把它拆成「先判断,再按专门的 prompt 回复」,两步各自的质量都会上升。
这就引出了 Workflow 的核心思想:
把一个大任务拆成几个小任务,每次只让模型做一件事;至于这些小任务怎么串起来,由你的代码决定,不由模型决定。
三、概念:三种基本形状
Anthropic 在《Building Effective Agents》里把这件事讲得最清楚。他们先划了一条分界线:
| 定义 | |
|---|---|
| Workflow | 路径预先定义好的系统,模型只在每个格子里干活 |
| Agent | 模型自己决定路径的系统 |
然后归纳出五种基本模式。本篇讲其中三种最基础的,另外两种因为已经越过分界线,放到后面单独讲:
| 模式 | 中文 | 在哪讲 |
|---|---|---|
| Prompt Chaining | 链式 | 本篇 |
| Parallelization | 并行 | 本篇 |
| Routing | 路由 | 本篇 |
| Orchestrator-Workers | 主管-工人 | 05 |
| Evaluator-Optimizer | 评审-优化 | 04 |
三种形状用一句话概括:
判断该用哪个的口诀:
- 后一步需要前一步的结果 → 链式
- 几件事互不依赖 → 并行
- 只走其中一条路 → 路由
四、动手:三种形状分别搭出来
拆解用的是 anthropics/claude-cookbooks(51,826★,MIT 协议,可以直接抄进你的项目)里的 patterns/agents/basic_workflows.ipynb。
三个模式加起来不到 40 行代码。少到会让人怀疑「这也算模式?」—— 但形状简单不代表不值钱,值钱的是知道什么时候用哪个。
4.0 先准备一个最小的底座
三种形状共用同一个函数,就是「调一次模型,拿到文本」:
import os, re
from anthropic import Anthropic
def llm_call(prompt: str, system_prompt: str = "", model="claude-sonnet-4-6") -> str:
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
response = client.messages.create(
model=model,
max_tokens=4096,
system=system_prompt,
messages=[{"role": "user", "content": prompt}],
temperature=0.1, # ← 这个值待会儿单独讲
)
return response.content[0].text
注意和 01 的区别:这里没有 tools,也没有循环。 就是一问一答。Workflow 的每个格子都是这样一次性的调用。
4.1 链式:五行

图 2-1 链式(Prompt Chaining)的官方示意
先看代码有多简单:
def chain(input: str, prompts: list[str]) -> str:
result = input
for i, prompt in enumerate(prompts, 1):
result = llm_call(f"{prompt}\nInput: {result}")
return result
整个模式就是 result 被反复覆盖。 第一步的输出变成第二步的输入,以此类推。
怎么用它:
prompts = [
"从这段文字里提取所有数字和它们的含义,一行一个 :",
"把上面的内容转成两列的 Markdown 表格:",
"按数值从大到小重新排序:",
]
result = chain(用户输入的一大段文字, prompts)
为什么这么拆有用